﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Peter.Sdw.Data;
using Peter.Sdw.Properties;

namespace Peter.Sdw.Logic
{
   /// <summary>
   /// A queue for web requests.
   /// </summary>
   public class WebQueue : IDisposable
   {
      private bool m_IsGetting;
      private bool m_IsPosting;
      private static WebQueue s_Instance;
      private readonly CookieAwareWebClient m_WebClient;
      private readonly Queue<string> m_GetQueue = new Queue<string> ();
      private readonly Queue<PostData> m_PostQueue = new Queue<PostData> ();
      private const string BASE_ADDRESS = "http://localhost:17038/";

      /// <summary>
      /// Event that occurs when a web get request is completed.
      /// </summary>
      public event WebRequestEventHandler WebGetComplete;

      /// <summary>
      /// Event that occurs when a web post request is completed.
      /// </summary>
      public event WebRequestEventHandler WebPostComplete;

      /// <summary>
      /// Initializes a new web queue.
      /// </summary>
      public WebQueue ()
      {
         this.m_IsGetting = false;
         this.m_IsPosting = false;
         this.m_WebClient = new CookieAwareWebClient ();
      }

      /// <summary>
      /// Gets the one and only instance of the web queue.
      /// </summary>
      public static WebQueue Instance
      {
         get { return s_Instance ?? (s_Instance = new WebQueue ()); }
      }

      /// <summary>
      /// Gets or Sets the request token to append to posts.
      /// </summary>
      public string RequestToken { get; private set; }

      /// <summary>
      /// Gets if the web client has been authenticated or not.
      /// </summary>
      public bool IsAuthenticated { get; private set; }

      /// <summary>
      /// Parses out the requested token.
      /// </summary>
      /// <param name="tokenResponse">The token request response from the server.</param>
      private void ParseToken (string tokenResponse)
      {
         int index = tokenResponse.IndexOf ("value=", System.StringComparison.Ordinal);
         if (index < 0) return;
         string token = tokenResponse.Substring (index + 7);
         token = token.Substring (0, token.IndexOf ("\"", System.StringComparison.Ordinal));
         this.RequestToken = token;
      }

      /// <summary>
      /// Queues the given url for data retrevial.
      /// </summary>
      /// <param name="url">Url to request data from.</param>
      public void Get (string url)
      {
         this.m_GetQueue.Enqueue (url);
         if (!this.m_IsGetting)
            this.GetAll ();
      }

      /// <summary>
      /// Queues the given url for data retrevial.
      /// </summary>
      /// <param name="url">Url to request data from.</param>
      /// <param name="parameters">The list of parameters to post.</param>
      /// <param name="data">Additional data to pass along.</param>
      public void Post (string url, NameValueCollection parameters, object data = null)
      {
         this.m_PostQueue.Enqueue (new PostData {Url = url, Parameters = parameters, Data = data});
         if (!this.m_IsPosting)
            this.PostAll ();
      }

      /// <summary>
      /// Performs all requests in the queue.
      /// </summary>
      private async void GetAll ()
      {
         this.m_IsGetting = true;
         while (this.m_IsPosting) { await Task.Run (() => Thread.Sleep (10)); }
         while (true)
         {
            var responseText = string.Empty;
            var url = this.m_GetQueue.Dequeue ();
            try 
            {
               Console.WriteLine (Resources.WebQueue_Get + url);
               responseText = await this.m_WebClient.DownloadStringTaskAsync (new Uri (BASE_ADDRESS + url));
            }
            catch (Exception ex)
            {
               responseText += ex.Message;
               Console.Error.WriteLine (ex.Message);
            }
            if (url == Constants.URL_GET_TOKEN)
            {
               // This is the only request where we do not get JSON back.
               this.ParseToken (responseText);
               Console.WriteLine (Resources.WebQueue_Token_retreived);
               if (this.WebGetComplete != null)
                  this.WebGetComplete (s_Instance, new WebRequestEventArgs (url, responseText));
            }
            else
            {
               dynamic responseJObj = JObject.Parse (responseText);
               if (responseJObj.status == Constants.FAIL)
                  Console.Error.WriteLine (responseJObj.message);
               if (this.WebGetComplete != null)
                  this.WebGetComplete (s_Instance, new WebRequestEventArgs (url, responseJObj));
            }
            if (this.m_GetQueue.Count > 0)
               continue;
            break;
         }
         this.m_IsGetting = false;
      }

      /// <summary>
      /// Performs all requests in the queue.
      /// </summary>
      private async void PostAll ()
      {
         this.m_IsPosting = true;
         while (this.m_IsGetting) { await Task.Run (() => Thread.Sleep (10)); }
         while (true)
         {
            string responseText;
            var postData = this.m_PostQueue.Dequeue ();
            try
            {
               Console.WriteLine (Resources.WebQueue_Post + postData.Url);
               byte[] response = await this.m_WebClient.UploadValuesTaskAsync (new Uri (BASE_ADDRESS + postData.Url),
                  "POST", postData.Parameters);
               responseText = Encoding.UTF8.GetString (response);
            }
            catch (Exception ex)
            {
               Console.Error.WriteLine (ex.Message);
               if (this.m_PostQueue.Count > 0)
                  continue;
               break;
            }
            dynamic responseJObj = JObject.Parse (responseText);
            if (postData.Url == Constants.URL_LOGIN_REQUEST && responseJObj.status == Constants.SUCCESS)
            {
               this.RequestToken = string.Empty;
               this.IsAuthenticated = true;
            }
            if (responseJObj.status == Constants.FAIL)
               Console.Error.WriteLine (responseJObj.message);
            if (this.WebPostComplete != null)
               this.WebPostComplete (s_Instance, new WebRequestEventArgs (postData.Url, responseJObj, postData.Data));
            if (this.m_PostQueue.Count > 0)
               continue;
            break;
         }
         this.m_IsPosting = false;
      }

      /// <summary>
      /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
      /// </summary>
      public void Dispose ()
      {
         this.m_WebClient.Dispose();
      }

      /// <summary>
      /// Data for posting.
      /// </summary>
      private class PostData
      {
         /// <summary>
         /// The url to post data to.
         /// </summary>
         public string Url { get; set; }

         /// <summary>
         /// The paramerts to post.
         /// </summary>
         public NameValueCollection Parameters { get; set; }

         /// <summary>
         /// Gets any additional data.
         /// </summary>
         public object Data { get; set; }
      }
   }
}
